Skip to content

Fail the job when altool rejects an upload - #3

Merged
sarensw merged 1 commit into
mainfrom
fix-altool-version-collision-swallowed-as-success
Aug 6, 2026
Merged

Fail the job when altool rejects an upload#3
sarensw merged 1 commit into
mainfrom
fix-altool-version-collision-swallowed-as-success

Conversation

@sarensw

@sarensw sarensw commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

distribute-beta and distribute-release reported green jobs on failed App Store uploads. On LeanBytes/framebison two consecutive betas never reached TestFlight while both runs went green — the only way to see it was reading the raw altool log.

The bug

The benign-case guard was meant to tolerate a genuine redundant upload (same version and build already on ASC, ITMS-90189). It matched on already been (used|uploaded). Apple phrases the build-number collision as:

The provided entity includes an attribute with a value that has already been used (-19232) The bundle version must be higher than the previously uploaded version: '1'.

Same branch. So a real failure needing a higher CFBundleVersion was treated as an idempotent re-run. RC was captured from altool and never checked, leaving the decision purely textual.

The fix

  • Narrow the benign pattern to ITMS-90189 / redundant binary upload.
  • Accept RC == 0 up front, so a success with unexpected wording isn't misread as a failure.
  • Extract classify_upload into .github/scripts/classify-upload.sh. The logic was duplicated inline in both workflows — which is precisely how one defect came to exist in two places. Both publish jobs already check out .shared-ci, so this adds no new plumbing.
  • Cover it in tests/run.sh, sourcing the shipped script rather than re-implementing it, so the test can't drift from what actually runs.

Behaviour change

altool result before after
UPLOAD SUCCEEDED accepted accepted
exit 0, unexpected wording failed accepted
-19232 build-number collision already-present failed
ITMS-90189 redundant upload already-present already-present
opaque altool error failed failed

Two rows change. The third is the reported bug; the second is a false-negative fixed in passing.

Verification

== classify_upload: altool outcome classification ==
  ok  : clean success → accepted
  ok  : exit 0, quiet output → accepted
  ok  : build-number collision (-19232) → failed
  ok  : true redundant upload (ITMS-90189) → already-present
  ok  : opaque altool error → failed

ALL TESTS PASSED ✅

The collision fixture is verbatim from the framebison run that shipped green. actionlint clean with the same ignores selftest.yml uses.

Note for reviewers

This is a behaviour change for every product on v0.4.x: uploads that previously passed silently will now fail the job. That's the point — but if any product has been relying on the old leniency, its next release will start failing. I'd expect that only where a build number is genuinely colliding, which is a bug in that product.

Not merged — left for review. Needs a version tag (v0.4.9) to reach callers, since they pin @v0.4.8.

Closes #2

distribute-beta and distribute-release reported green jobs on failed App
Store uploads. On LeanBytes/framebison two consecutive betas never reached
TestFlight while both runs went green; the only way to see it was reading
the raw altool log.

The benign-case guard was meant to tolerate a genuine redundant upload —
same version and build already on ASC, which Apple reports as ITMS-90189.
It matched on "already been (used|uploaded)". Apple phrases the build
number collision as "an attribute with a value that has already been
used (-19232) The bundle version must be higher than the previously
uploaded version", so a real failure needing a higher CFBundleVersion hit
the same branch and was treated as an idempotent re-run. RC was captured
from altool and never checked, leaving the decision purely textual.

Narrow the benign pattern to ITMS-90189 / "redundant binary upload", and
accept RC == 0 up front so a success with unexpected wording is not
misread as a failure.

The logic was duplicated inline in both workflows, which is how one
defect came to exist in two places, so extract it as classify_upload in
.github/scripts/classify-upload.sh and source it from each. tests/run.sh
covers the three outcomes against real altool output, sourcing the
shipped script so the test cannot drift from what runs. The collision
case is verbatim from the run that shipped green.

Closes #2
@sarensw
sarensw merged commit 8ab65d7 into main Aug 6, 2026
1 check passed
sarensw added a commit that referenced this pull request Aug 6, 2026
selftest was the last ubuntu-latest job in the repo, and also the only
workflow that stopped dispatching: no run for PRs #3 or #5, none for
either merge to main, zero check runs created, and reopening a PR to
re-fire pull_request changed nothing. Every self-hosted job kept running
throughout.

GitHub-hosted runners are a separate capacity and billing path from our
own, so a block there takes out exactly this job and nothing else. The
correlation is one-to-one: the only hosted job is the only broken one.

Nothing here needs Linux — actionlint ships a darwin build and the tests
are offline python3 plus git.

Install actionlint into RUNNER_TEMP rather than the workspace. A
self-hosted runner reuses its checkout, so ./actionlint would otherwise
sit untracked in the working tree between runs.

Also drop two comments left stale by v0.4.7, which moved every job to
self-hosted but kept describing the prepare jobs as running on ubuntu.
Comment-only, so callers are unaffected and no new tag is needed;
selftest is not a reusable workflow, so no pin changes either.

Closes #6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

altool version-collision failures are reported as successful uploads

1 participant